/*
 * @Author: ellery
 * @Date: 2022-08-21 18:21:36
 * @LastEditTime: 2022-08-21 23:54:57
 * @LastEditors: ellery
 * @Description:axios请求封装
 * @FilePath: \vue2-eslint-model\src\api\api.js
 * Copyright (c) 2022 by ellery, All Rights Reserved.
 * 愿天堂没代码
 */

import Axios from "axios";

const BASE_CONFIG = {
  baseURL: "",
  timeout: 1000 * 60 * 2,
  method: "POST"
};
const DEFAULT_DENY_CHARS = ["", null, -1];

const EMPTY_OBJECT = {};

// const EMPTY_FUNC = function() {};

let aixos;

export default class Api {
  constructor({
    apiUrls = EMPTY_OBJECT,
    staticUrls = EMPTY_OBJECT, // 静态访问地址
    baseConfig = EMPTY_OBJECT,
    codeCatcher, // 状态码
    bodyAllowChars = [],
    paramsAllowChars = []
  } = {}) {
    if (codeCatcher === undefined) throw Error("codeCatcher 是必须的");
    this._apiUrls = apiUrls;
    this._staticUrls = staticUrls;
    this._baseConfig = {
      ...baseConfig,
      ...BASE_CONFIG
    };
    // 创建axios实例
    this.aixos = aixos = Axios.create({
      ...baseConfig
    });
    // 添加响应拦截器
    aixos.interceptors.response.use(
      (response) => this._transformResponse(response),
      (error) => {
        this._trigger("REQUEST", error);
        return Promise.resolve({
          success: false,
          response: error
        });
      }
    );
    // 添加请求拦截器
    aixos.interceptors.request.use((config) => this._onRequest(config));
    this._buildStaticUrls(staticUrls);
    this._buildApiUrls(apiUrls, this);
    this.$codeCatcher = codeCatcher;
    this.$map = new Map(); // 定义一个空的map
    this.$resCatchMap = new Map();
    this.$bodyAllowChars = bodyAllowChars;
    this.$paramsAllowChars = paramsAllowChars;
  }
  // 请求失败时
  $catch(value, cb) {
    if (Array.isArray(value)) {
      value.forEach((val) => {
        this.$resCatchMap.set(val, cb);
      });
    } else {
      this.$resCatchMap.set(value, cb);
    }
  }
  // 监听请求
  $on(value, cb, scope) {
    if (this.$map.get(value) === undefined) {
      this.$map.set(value, []);
    }
    this.$map.get(value).push({
      fn: cb,
      scope
    });
  }
  // 注销监听请求
  $off(value, cb) {
    const cbList = this.$map[value];
    if (cbList) {
      const index = cbList.findIndex((fun) => fun === cb);
      cbList.splice(index, 1);
    }
  }
  // 监听一次请求
  $once(value, cb, scope) {
    const _cb = (data, arg) => {
      cb(data, arg);
      this.off(value, _cb);
    };
    this.on(value, _cb, scope);
  }
  // 触发请求时,this.$map ==> {0=>{REQUEST:error},1=>{RESPONSE:response},3=>{ERROR:err}}
  _trigger(value, params) {
    const cbList = this.$map.get(value);
    if (cbList !== undefined) {
      cbList.forEach(({ fn, scope }) => {
        if (scope) {
          fn.call(scope, params);
        } else {
          fn(params);
        }
      });
    }
  }
  // 处理响应时返回的数据
  _onRequest(config) {
    let newConfig = {};
    const cbList = this.$map.get("REQUEST");
    if (cbList !== undefined) {
      cbList.forEach(({ fn, scope }) => {
        let _config;
        if (scope) {
          _config = fn.call(scope, config);
        } else {
          _config = fn(config);
        }
        newConfig = {
          ...newConfig,
          ..._config
        };
      });
    }
    // console.log(newConfig);
    return {
      ...config,
      ...newConfig
    };
  }
  // 在传递给 then/catch 前，允许修改响应数据
  _transformResponse(response) {
    const { data } = response;
    let resData;
    const value = this.$codeCatcher(data, response);
    const cb = this.$resCatchMap.get(value);
    if (cb !== undefined) {
      resData = cb(response);
      // console.log(resData)
    }
    this._trigger("RESPONSE", response);
    return Promise.resolve({
      success: false,
      response,
      data: response.data,
      ...resData
    });
  }
  // 处理静态接口地址，因用了路由插件，$表示get请求,所以得处理url地址;若无使用路由插件可以忽略。
  _buildStaticUrls(staticUrls) {
    const urls = staticUrls;
    Object.keys(urls).forEach((key) => {
      const url = urls[key];
      if (url.indexOf("$") === 0) {
        this[key] = url.slice(1);
      } else if (url.indexOf("http") !== 0) {
        this[key] = this._baseConfig.baseURL + url;
      }
    });
  }
  // 处理Ajax请求地址
  _buildApiUrls(apiUrls = {}, list = {}) {
    Object.keys(apiUrls).forEach((key) => {
      const val = apiUrls[key];
      switch (val.constructor) {
        case String:
          list[key] = this._createAxios({
            ...this._parseUrl(val)
          });
          break;
        case Object:
          if (val.url !== undefined) {
            list[key] = this._createAxios({
              ...val
            });
          } else {
            list[key] = this._buildApiUrls(val);
          }
      }
    });
    return list;
  }
  // 因用了路由插件，$表示get请求,否则默认为post
  _parseUrl(url) {
    return url.indexOf("$") === 0
      ? {
        url: url.slice(1),
        method: "GET"
      }
      : {
        url,
        method: "POST"
      };
  }
  // 创建请求
  _createAxios(api) {
    return (options = {}) => {
      const config = {
        ...api,
        ...options
      };
      const {
        data, // post ，put，putch等传值 -> data:{test:""}
        params, // get传值 -> params:{test:""}
        query // 路径传参
      } = config;
      let {
        bodyAllowChars = [], // 请求data字符过滤白名单
        paramsAllowChars = [] // 请求params字符过滤白名单
      } = config;
      bodyAllowChars = new Set([...this.$bodyAllowChars, ...bodyAllowChars]);
      paramsAllowChars = new Set([
        ...this.$paramsAllowChars,
        ...paramsAllowChars
      ]);
      if (data !== undefined && !(data instanceof FormData)) {
        config.data = removeEmptyProps(data, Array.from(bodyAllowChars));
      }
      if (params != undefined) {
        config.params = removeEmptyProps(params, Array.from(paramsAllowChars));
      }
      if (query && /\{\w+?\}/g.test(config.url)) {
        // 判断是否是url格式
        Object.keys(options.query).forEach((key) => {
          const val = options.query[key];
          config.url = config.url.replace(new RegExp(`\{${key}\}`), val);
        });
      }

      return aixos.request(config).catch((err) => {
        this._trigger("ERROR", err);
      });
    };
  }
}
// 处理空格
function removeEmptyProps(ob, allowChars = []) {
  const result = {};
  const map = allowChars.length
    ? DEFAULT_DENY_CHARS.concat(allowChars).filter(function(v) {
      return (
        DEFAULT_DENY_CHARS.indexOf(v) === -1 || allowChars.indexOf(v) === -1
      );
    })
    : DEFAULT_DENY_CHARS;

  for (const key in ob) {
    const val = ob[key];
    const type = typeof val;
    if (!map.includes(type === "string" ? val.trim() : val)) {
      result[key] = ob[key];
    }
  }
  return result;
}
